# -*- coding: binary -*-

module Msf
  module Exploit::Remote::SMB::Server
    module Share
      module Command
        module NtCreateAndx

          # Handles an SMB_COM_NT_CREATE_ANDX command, used by the client to create and
          # open a new file.
          #
          # @param c [Socket] The client sending the request.
          # @param buff [String] The data including the client request.
          # @return [Integer] The number of bytes returned to the client as response.
          def smb_cmd_nt_create_andx(c, buff)
            smb = @state[c]
            pkt = CONST::SMB_CREATE_PKT.make_struct
            pkt.from_s(buff)

            payload = pkt['Payload'].v['Payload'].downcase

            if pkt['Payload']['SMB'].v['Flags2'] & CONST::FLAGS2_UNICODE_STRINGS == CONST::FLAGS2_UNICODE_STRINGS
              # If path length is odd first character is alignment
              if payload.length.odd?
                payload = payload[1..-1]
              end
              payload = Rex::Text.to_ascii(payload)
            end

            payload.gsub!(/[\x00]*/, '') # delete nulls

            if payload.nil? || payload.empty?
              payload = file_name
            end

            contents = get_file_contents(client: c)

            if payload.ends_with?(file_name.downcase)
              vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX request for #{unc}... ")
              fid = smb[:file_id].to_i
              attribs = CONST::SMB_EXT_FILE_ATTR_NORMAL
              eof = contents.length
              is_dir = 0
            elsif folder_name && payload.ends_with?(folder_name.downcase)
              fid = smb[:dir_id].to_i
              attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
              eof = 0
              is_dir = 1
            elsif payload == "\\"
              fid = smb[:dir_id].to_i
              attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
              eof = 0
              is_dir = 1
            else
              # Otherwise send not found
              vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX for #{payload}, not found")
              return smb_error(CONST::SMB_COM_NT_CREATE_ANDX, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
            end

            send_nt_create_andx_res(c, {
              file_id: fid,
              attributes: attribs,
              end_of_file_low: eof,
              is_directory: is_dir,
              alloc_low: 0x100000
            })
          end

          # Builds and sends an SMB_COM_NT_CREATE_ANDX response.
          #
          # @param c [Socket] The client to answer.
          # @param opts [Hash{Symbol => <Integer>}] Response custom values.
          # @option opts [Integer] :file_id A FID representing the file or directory created or opened.
          # @option opts [Integer] :attributes The attributes that the server assigned to the file or directory.
          # @option opts [Integer] :end_of_file_low The end of file offset value (4 bytes)
          # @option opts [Integer] :is_directory Indicates if the FID represents a directory.
          # @option opts [Integer] :alloc_low The number of bytes allocated to the file by the server.
          # @return [Integer] The number of bytes returned to the client as response.
          def send_nt_create_andx_res(c, opts = {})
            file_id = opts[:file_id] || 0
            attributes = opts[:attributes] || 0
            end_of_file_low = opts[:end_of_file_low] || 0
            is_directory = opts[:is_directory] || 0
            alloc_low = opts[:alloc_low] || 0

            pkt = CONST::SMB_CREATE_ANDX_RES_PKT.make_struct
            smb_set_defaults(c, pkt)
            pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
            pkt['Payload']['SMB'].v['Flags1'] = FLAGS
            pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
            pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NT_CREATE_ANDX_RES_WORD_COUNT
            pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND
            pkt['Payload'].v['OpLock'] = CONST::LEVEL_II_OPLOCK # Grant Oplock on File
            pkt['Payload'].v['FileID'] = file_id
            pkt['Payload'].v['Action'] = CONST::FILE_OPEN # The file existed and was opened
            pkt['Payload'].v['CreateTimeLow'] = lo
            pkt['Payload'].v['CreateTimeHigh'] = hi
            pkt['Payload'].v['AccessTimeLow'] = lo
            pkt['Payload'].v['AccessTimeHigh'] = hi
            pkt['Payload'].v['WriteTimeLow'] = lo
            pkt['Payload'].v['WriteTimeHigh'] = hi
            pkt['Payload'].v['ChangeTimeLow'] = lo
            pkt['Payload'].v['ChangeTimeHigh'] = hi
            pkt['Payload'].v['Attributes'] = attributes
            pkt['Payload'].v['AllocLow'] = alloc_low
            pkt['Payload'].v['AllocHigh'] = 0
            pkt['Payload'].v['EOFLow'] = end_of_file_low
            pkt['Payload'].v['EOFHigh'] = 0
            pkt['Payload'].v['FileType'] = CONST::SMB_RESOURCE_FILE_TYPE_DISK
            pkt['Payload'].v['IPCState'] = 0x7 # Number maxim of instance a named pipe can have
            pkt['Payload'].v['IsDirectory'] = is_directory
            pkt['Payload'].v['MaxAccess'] = CREATE_MAX_ACCESS
            c.put(pkt.to_s)
          end
        end
      end
    end
  end
end
